/**************************************************************************
* This file is part of Hawkengine.
*
* Hawkengine is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Hawkengine is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Hawkengine.  If not, see <http://www.gnu.org/licenses/>.
**************************************************************************/


/**************************************************************************
* Dependencies
**************************************************************************/
#include "engine/network.h"

#if defined(NETWORK_STANDARD)
#include <errno.h>
#include <unistd.h>
#include <arpa/inet.h>

#elif defined(NETWORK_WIN)
#include <winsock.h>

#else

#endif

/**************************************************************************
* Preprocessing
**************************************************************************/
#define MAX_PENDING_CONNECTIONS 5

/**************************************************************************
* Types
**************************************************************************/

/**************************************************************************
* Variables
**************************************************************************/

/**************************************************************************
* Static Variables
**************************************************************************/

/**************************************************************************
* Function Prototypes
**************************************************************************/

/**************************************************************************
* Function Implementations
**************************************************************************/
#if defined(NETWORK_STANDARD) || defined(NETWORK_WIN)
static int get_last_error(void)
{
#if defined(NETWORK_STANDARD)
  return errno;
#elif defined(NETWORK_WIN)
  return WSAGetLastError();
#else
  return 0;
#endif
}
BOOL network_bind(network_type* network, int port)
{
  struct sockaddr_in addr;

  if ((network->id <= 0) || network->bound) {
    return FALSE;
  }

  addr.sin_family = AF_INET;
  *((ULONG*)&(addr.sin_addr)) = network->address = htonl(INADDR_ANY);
  network->port = port;
  addr.sin_port = htons(network->port);

  if (bind(network->id, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
    log_message(LOG_LEVEL_ERROR, "Could not bind socket[%d] to port %d: %d",
                network->id, port, get_last_error());
    return FALSE;
  }

  network->bound = TRUE;
  log_message(LOG_LEVEL_INFO, "Bound socket[%d] to port %d", network->id, port);

  return TRUE;
}

void network_close(network_type* network)
{
  if (network->id > 0) {
#if defined(NETWORK_STANDARD)
    close(network->id);
#elif defined(NETWORK_WIN)
    closesocket(network->id);
#endif
  }

  memset(network, 0, sizeof(network_type));
}

BOOL network_connect(network_type* network, const char* host, int port)
{
  struct sockaddr_in addr;

  if ((network->id <= 0) || network->connected) {
    return FALSE;
  }

  addr.sin_family = AF_INET;
  *((ULONG*)&(addr.sin_addr)) = network->address = inet_addr(host);
  network->port = port;
  addr.sin_port = htons(network->port);

  if (connect(network->id, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
    log_message(LOG_LEVEL_ERROR, "Could not connect socket[%d] to %s:%d: %d",
                network->id, host, port, get_last_error());
    return FALSE;
  }

  network->connected = TRUE;

  log_message(LOG_LEVEL_INFO, "Connected socket[%d] to %s:%d", network->id,
              host, port);

  return TRUE;
}

BOOL network_create(network_type* network, network_protocol_type protocol)
{
  memset(network, 0, sizeof(network_type));
  network->protocol = protocol;

  switch (network->protocol) {
  case NETWORK_PROTOCOL_TCP:
    network->id = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    break;
  case NETWORK_PROTOCOL_UDP:
    network->id = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    break;
  default:
    return FALSE;
  }

  if (network->id <= 0) {
    log_message(LOG_LEVEL_ERROR, "Could not create socket: %d",
                get_last_error());
    return FALSE;
  } else {
    return (network->id > 0);
  }
}

void network_init(void)
{
#if defined(NETWORK_WIN) 
  WSADATA wsadata;
  int error = WSAStartup(0x0202, &wsadata);
  if (error) {
    log_message(LOG_LEVEL_ERROR, "Could not initialize WinSock networking.");
    return;
  }
#endif
}

int network_receive(const network_type* network, network_type* client,
                    int max_bytes, UCHAR* data)
{
  int bytes_received;

  if (network->id <= 0) {
    return -1;
  }

  switch (network->protocol) {
  case NETWORK_PROTOCOL_TCP:
    {
      if (client->id <= 0) {
        return -1;
      }
      bytes_received = recv(client->id, data, max_bytes, 0);
    }
    break;
  case NETWORK_PROTOCOL_UDP:
    {
      struct sockaddr addr;
      UINT client_length = (int)sizeof(addr);
      memset(client, 0, sizeof(network_type));
      bytes_received = recvfrom(network->id, data, max_bytes, 0, &addr, &client_length);
    }
    break;
  default:
    break;
  }

  if (bytes_received < 0) {
    log_message(LOG_LEVEL_ERROR, "Could not receive on socket[%d] from client[%d]: %d",
                network->id, client->id, get_last_error());
  }

  return bytes_received;
}

int network_send(const network_type* network, UCHAR* data, int bytes)
{
  int bytes_sent = 0;

  if (!(network->connected)) {
    return -1;
  }

  bytes_sent = send(network->id, data, bytes, 0);
  if (bytes_sent < 0) {
    log_message(LOG_LEVEL_ERROR, "Could not send on socket[%d]: %d",
                network->id, get_last_error());
  }

  return bytes_sent;
}

void network_uninit(void)
{
}

BOOL network_wait_for_client(const network_type* network, network_type* client)
{
  struct sockaddr_in addr;
  UINT client_length;
  memset(client, 0, sizeof(network_type));

  if (!(network->bound) || (network->protocol != NETWORK_PROTOCOL_TCP)) {
    return FALSE;
  }

  if (listen(network->id, MAX_PENDING_CONNECTIONS) < 0) {
    log_message(LOG_LEVEL_ERROR, "Could not listen on socket[%d]: %d",
                network->id, get_last_error());
    return FALSE;
  }

  log_message(LOG_LEVEL_INFO, "Socket[%d] listening", network->id);

  client_length = sizeof(addr);
  client->id = accept(network->id, (struct sockaddr*)&addr, &client_length);
  client->address = addr.sin_addr.s_addr;
  client->port = ntohs(addr.sin_port);
  if (client->id < 0) {
    log_message(LOG_LEVEL_ERROR, "Could not accept client connection on socket[%d]: %d",
                network->id, get_last_error());
    return FALSE;
  }

  log_message(LOG_LEVEL_DEBUG, "Socket[%d] received connection from client[%d]@%s:%d",
              network->id, client->id, inet_ntoa(*((struct in_addr*)&(client->address))),
              ntohs(client->port));

  return TRUE;
}

#elif defined(NETWORK_CUSTOM)

#else
// no network
BOOL network_bind(network_type* network, int port) { return FALSE; }
void network_close(network_type* network) { }
BOOL network_connect(network_type* network, const char* host, int port) { return FALSE; }
BOOL network_create(network_type* network, network_protocol_type protocol) { return FALSE; }
void network_init(void) { }
int network_receive(const network_type* network, network_type* client,
                    int max_bytes, UCHAR* data) { return 0; }
int network_send(const network_type* network, UCHAR* data, int bytes) { return 0; }
void network_uninit(void) { }
BOOL network_wait_for_client(const network_type* network, network_type* client) { return FALSE; }

#endif

